﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.IO;
using System.Diagnostics;

using P2PLib.Client.DataModel;
using P2PLib.Common.DataModel;
using P2PLib.Client.Command;

namespace P2PLib.Client.ViewModel
{
    /// <summary>
    /// 该类代表了一个下载任务;
    /// </summary>
    public class DownloadTaskViewModel:INotifyPropertyChanged
    {
        private CFileInfo file;
        private long completedSize;
        private int speed;
        private DateTime startedTime;
        private string localDir;
        private DownloadStatus status;

        private ClientInfo client;

        private ObservableCollection<ClientInfo> source;
        private NetworkStream downloadStream;

        private BackgroundWorker bgCommandReceiver;

        private BackgroundWorker bgDataReceiver;

        #region property
        public CFileInfo File
        {
            get
            {
                return file;
            }
            set
            {
                this.file = value;
                this.Notify("File");
            }
        }

        public long CompletedSize
        {
            get
            {
                return completedSize;
            }
            set
            {
                this.completedSize = value;
                this.Notify("CompletedSize");
            }
        }

        public int Speed
        {
            get { return this.speed;  }
            set { this.speed = value;
            this.Notify("Speed");
            }

        }

        public DateTime StartedTime
        {
            get { return startedTime; }
            set { this.startedTime = value;
            this.Notify("StartedTime");
            }
        }

        public ObservableCollection<ClientInfo> Source
        {
            get { return source; }
            set
            { 
                this.source = value;
                this.Notify("Source");
            }
        }

        public string LocalDir
        {
            get
            {
                return this.localDir;
            }
            set
            {
                this.localDir = value;
            }
        }

        public DownloadStatus Status
        {
            get
            {
                return this.status;
            }
            set
            {
                this.status = value;
            }
        }
        #endregion //property

        #region constructor
        public DownloadTaskViewModel(DownloadTask task)
        {
            this.file = task.File;
            this.completedSize = task.CompletedSize;
            this.speed = 0;
            this.startedTime = task.StartedTime;
            this.status = task.Status;
            this.localDir = task.LocalDir;
            this.source = new ObservableCollection<ClientInfo>();

            //create local file;
            this.CreateFile();
        }

        public DownloadTaskViewModel(SearchResultViewModel srvm, string localDir, string name)
        {
            this.file = srvm.File;
            this.localDir = localDir;
            this.file.FileName = name;
            this.speed = 0;
            this.completedSize = 0;
            this.status = DownloadStatus.Stoped;
            this.startedTime = DateTime.Now;
            this.source = new ObservableCollection<ClientInfo>();

            foreach (ClientInfo c in srvm.Source)
            {
                this.source.Add(c);
            }

            //create local file;
            this.CreateFile();
        }
        #endregion 

        #region event
        /// <summary>
        /// occurs when download task need new source;
        /// </summary>
        public event SearchEventHandler SearchSource;

        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region public methods

        /// <summary>
        /// Start this download task
        /// </summary>
        public void Start()
        {
            //set the status to connecting 
            this.status = DownloadStatus.Connecting;

            BackgroundWorker bgConnector = new BackgroundWorker();
            bgConnector.DoWork += new DoWorkEventHandler(bgConnector_DoWork);
            bgConnector.WorkerSupportsCancellation = true;
            bgConnector.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgConnector_RunWorkerCompleted);
            bgConnector.RunWorkerAsync();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void bgConnector_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Debug.WriteLine("Connected...");
        }

        /// <summary>
        /// use souce to conect to a remote client,and wait 
        /// response
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void bgConnector_DoWork(object sender, DoWorkEventArgs e)
        {
            ClientInfo selectedClient = null;
            int selectPort = 0;

            while (this.status == DownloadStatus.Connecting&&this.source.Count>0)
            {
                ClientInfo client = this.source[0];
                this.source.RemoveAt(0);

                Debug.WriteLine("Download task:connecting to client at " + client.IP.ToString());
                
                //try to connect to the remote client
                try
                {
                    Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    s.Connect(new IPEndPoint(client.IP,client.Port));
                    NetworkStream stream = new NetworkStream(s);
                    //send request file command to remote client;
                    Debug.WriteLine("DTVM:sending request file command to client " + client.IP.ToString());
                    CCRequestFileCommand rfc = new CCRequestFileCommand(this.file.FileHashCode);
                    byte[] buffer = BitConverter.GetBytes((int)rfc.Type);
                    stream.Write(buffer, 0, buffer.Length);

                    rfc.WriteToStream(stream);

                    //read command from client;
                    stream.Read(buffer, 0, 4);
                    CCCommandType cmdType = (CCCommandType)BitConverter.ToInt32(buffer, 0);
                    Debug.WriteLine("DTVM:recieved command type: " + cmdType);
                    if (cmdType.Equals(CCCommandType.FileReady))
                    {
                        CCFileReadyCommand frc=new CCFileReadyCommand();
                        frc.ReadFromStream(stream);
                        Debug.WriteLine("DTVM: file ready..." + file.FileHashCode);
                        this.status = DownloadStatus.Waiting;
                        selectedClient = client;
                        selectPort = frc.Port;
                    }
                    else
                    {
                        Debug.WriteLine("DTVM:client " + client.IP.ToString() + " is out of date.");
                    }
                }
                catch
                {
                    Debug.WriteLine("connecting to client " + client.IP + " failed.");
                }
            }

            //循环结束，如果当前下载状态为Waiting则说明已经得到一个确认的FileServer
            if (this.status == DownloadStatus.Waiting)
            {
                //connect to the file server;
                this.ConnectToFileServer(selectedClient.IP, selectPort);
            }



        }

        /// <summary>
        /// connect to file server；
        /// </summary>
        /// <param name="port"></param>
        private void ConnectToFileServer(IPAddress ip, int port)
        {
             bool fileReady = false;
            try
            {

                Debug.Write("DTVM:connectiong to file sever at :" + ip.ToString() + "," + port);
                //connect to file server;
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.Connect(new IPEndPoint(ip, port));
                NetworkStream netStream = new NetworkStream(socket);
            
                while (!fileReady)
                {
                    //send start position command to file server;
                    CCFileStaratPositionCommand fspc = new CCFileStaratPositionCommand(this.completedSize);
                    byte[] buffer = new byte[4];
                    buffer = BitConverter.GetBytes((int)fspc.Type);
                    netStream.Write(buffer, 0, 4);
                    fspc.WriteToStream(netStream);

                    //wait for response;
                    netStream.Read(buffer, 0, 4);
                    CCCommandType cmdType = (CCCommandType)BitConverter.ToInt32(buffer, 0);
                    switch (cmdType)
                    {
                        case CCCommandType.StartRecieving:
                            //set the downloadstream to netstream and fileready to true
                            this.downloadStream = netStream;
                            fileReady = true;
                            break;
                        case CCCommandType.CurrentPosition:
                            //output the current position of client in the downloading queue;
                            CCCurrentPositionCommand cpc = new CCCurrentPositionCommand();
                            cpc.ReadFromStream(netStream);
                            Debug.WriteLine("DTVM:current position in the queue is " + cpc.Position);
                            break;
                        default:
                            Debug.WriteLine("unexpected command");
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("DTVM: expection...message " + ex.Message + Environment.NewLine + ex.StackTrace);
            }
            if (fileReady)
            {
                this.StartRecievingFile();
            }

        }


     
       
        /// <summary>
        /// 停止该下载任务;
        /// </summary>
        public void Stop()
        {

        }

        /// <summary>
        /// 打开该任务对应的本地文件
        /// </summary>
        public void OpenFile()
        {
            if (this.status == DownloadStatus.Completed)
            {

            }
        }

        #endregion //public methods


        #region private methods
        private void Notify(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// after setting the download stream to a remote client
        /// then start reading data from the downloadstream and
        /// write the data to local file
        /// </summary>
        private void StartRecievingFile()
        {
            Debug.WriteLine("DTVM: start downloading file");

            try
            {
                //open file stream;
                FileStream fs = new FileStream(this.localDir + "\\" + this.file.FileName,FileMode.Open);

                byte[] buffer = new byte[8192];
                int readBytes = 0;

                while ((readBytes = downloadStream.Read(buffer, 0, buffer.Length)) != 0)
                {
                    //write the bytes to file
                    fs.Write(buffer, 0, readBytes);
                    completedSize += readBytes;
                }

                Debug.WriteLine("DTVM:finished downloading file...");

            }
            catch (Exception ex)
            {
                Debug.WriteLine("DTVM: exception occured:" + ex.Message + Environment.NewLine + ex.StackTrace);
            }
        }

        /// <summary>
        /// create a file in local system
        /// </summary>
        private void CreateFile()
        {
            Debug.WriteLine("DTVM:creating file");

            string path = this.localDir + "\\"+this.file.FileName;

            FileInfo fileInfo = new FileInfo(path);

            if (!fileInfo.Exists)
            {
                fileInfo.Create();
            }
        }
        #endregion //private methods
    }
}
